﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace ChatServer
{
    public class StatusChangedEventArgs : EventArgs
    {
        //event argument, dit zal het bericht worden wat de gebruiker te zien krijgt
        private string eventMsg;

        //zet in server.cs de message in de event handler
        public string eventMessage
        {
            get
            {
                return eventMsg;
            }
            set
            {
                eventMsg = value;
            }
        }

        //set event message
        public StatusChangedEventArgs(string strEventMessage)
        {
            eventMsg = strEventMessage;
        }
    }

    //hier komen de parameters voor de events in
    public delegate void StatusChangedEventHandler(Object sender, StatusChangedEventArgs e);

    class Monitor
    {
        //hashtable waar gebruikers en connecties in komen te zitten. max 30
        public static Hashtable UserTable = new Hashtable(30);
        public static Hashtable ConnectionTable = new Hashtable(30);

        //variabelen voor server
        private IPAddress ip;
        private int port;
        private TcpClient server;

        //event die later gebruikt wordt
        public static event StatusChangedEventHandler StatusChangedEvent;
        private static StatusChangedEventArgs e;

        //luistert naar binnenkomende connecties
        private TcpListener tcpListener;

        //thread die naar binnenkomende connecties luistert
        private Thread threadListener;

        //als de boolean true is zal de server starten
        Boolean running = false;

        //vangt doorgestuurde data af
        public Monitor(IPAddress ipAddress, int serverPort)
        {
            ip = ipAddress;
            port = serverPort;
        }

        public void StartMonitoring()
        {
            //haal ip adres binnen
            IPAddress localIP = ip;
            int localPort = port;

            //start de listener en begin met het controleren of personen connecten
            tcpListener = new TcpListener(localIP, localPort);
            tcpListener.Start();
            running = true;

            //start de thread die de host laat listenen
            threadListener = new Thread(KeepListening);
            threadListener.Start();
        }

        private void KeepListening()
        {
            //terwijl de server runt
            while (running == true)
            {
                //accepteer connectie
                server = tcpListener.AcceptTcpClient();
                //maak nieuwe connectie voor de gebruiker aan
                Connection newConnection = new Connection(server);
            }
        }

        public static void AddUser(TcpClient tcpUser, string Username)
        {
            //add gegevens in hashtables
            Monitor.UserTable.Add(Username, tcpUser);
            Monitor.ConnectionTable.Add(tcpUser, Username);

            //vertelt iedereen dat er iemand connected is
            SendAdminMessage(ConnectionTable[tcpUser]+ " has joined the server");
        }

        public static void RemoveUser(TcpClient tcpUser)
        {
            //als de gebruiker er nog is
            if (ConnectionTable[tcpUser] != null)
            {
                SendAdminMessage(ConnectionTable[tcpUser] + " has left the server");

                //haal data uit tabellen
                Monitor.UserTable.Remove(ConnectionTable[tcpUser]);
                Monitor.ConnectionTable.Remove(tcpUser);
            }
        }

        public static void SendAdminMessage(string Message)
        {
            StreamWriter adminMessageSender;

            //laten zien wie wat zegt
            e = new StatusChangedEventArgs("Administrator: "+Message);
            OnStatusChanged(e);

            //array van tcp clients maken (zelfde als aantal connecties)
            TcpClient[] tcpclients = new TcpClient[Monitor.UserTable.Count];
            //tcp clients in array stoppen
            Monitor.UserTable.Values.CopyTo(tcpclients, 0);

            //loopen door de lijst zodat iedereen een bericht krijgt
            for (int i = 0; i < tcpclients.Length; i++)
            {
                try
                {
                    if (Message.Trim() == "" || tcpclients[i] == null)
                    {
                        continue;
                    }

                    adminMessageSender = new StreamWriter(tcpclients[i].GetStream());
                    adminMessageSender.WriteLine("Administrator: "+Message);
                    adminMessageSender.Flush();
                    adminMessageSender = null;
                }
                catch //als een gebruiker niet meer actief is verwijderen
                {
                    RemoveUser(tcpclients[i]);
                }
            }
        }

        public static void OnStatusChanged(StatusChangedEventArgs e)
        {
            StatusChangedEventHandler statusHandler = StatusChangedEvent;

            if (statusHandler != null)
            {
                statusHandler(null, e);
            }
        }

        public static void SendMessage(string From, string Message)
        {
            StreamWriter Sender;

            //laten zien wie wat zegt
            e = new StatusChangedEventArgs(From + " says: " + Message);
            OnStatusChanged(e);

            //array aanmaken van tcp clients, moet gelijk zijn aan het aantal users wat connected is
            TcpClient[] tcpClients = new TcpClient[Monitor.UserTable.Count];

            //tcpclient objecten in array zetten
            Monitor.UserTable.Values.CopyTo(tcpClients, 0);

            //loopen door tcp clients
            for (int i = 0; i < tcpClients.Length; i++)
            {
                //message sturen naar alle clients
                try
                {
                    if (Message.Trim() == "" || tcpClients[i] == null)
                    {
                        continue;
                    }
                    
                    //message sturen naar persoon in de loop
                    Sender = new StreamWriter(tcpClients[i].GetStream());
                    Sender.WriteLine(From + " says: " + Message);
                    Sender.Flush();
                    Sender = null;
                }
                catch
                {
                    RemoveUser(tcpClients[i]);
                }
            }
        }
    }

    class Connection
    {
        TcpClient tcpClient;

        //deze thread stuurt info naar de client
        private Thread Sender;

        //vangt messages af
        private StreamReader Recieve;

        //vangt user af
        private StreamWriter From;

        //username + message
        private String User;
        private String Response;

        //thread die connecties accepteert en op messages wacht
        public Connection(TcpClient tcpConnection)
        {
            tcpClient = tcpConnection;
            Sender = new Thread(AcceptClient);
            Sender.Start();
        }

        //sluit connecties
        private void CloseConnection()
        {
            tcpClient.Close();
            Recieve.Close();
            From.Close();
        }

        //run deze methode als een nieuwe client connect
        private void AcceptClient()
        {
            Recieve = new StreamReader(tcpClient.GetStream());
            From = new StreamWriter(tcpClient.GetStream());

            //account informatie uitlezen
            User = Recieve.ReadLine();

            //response van client afvangen
            if (User != "")
            {
                //checken of gebruikersnaam al bestaat
                if (Monitor.UserTable.Contains(User) == true)
                {
                    //0 == not connected
                    From.WriteLine("0|This username already exists");
                    From.Flush();
                    CloseConnection();
                    return;
                }
                //checken of gebruikersnaam niet "Administrator" is
                else if (User == "Administrator" || User == "Admin" || User == "administrator" || User == "admin")
                {
                    //0 == not connected
                    From.WriteLine("0|This username is reserved");
                    From.Flush();
                    CloseConnection();
                    return;
                }
                //als aan bovenstaande criteria voldaan word dan kan de user connecten
                else
                {
                    //1 betekent connected
                    From.WriteLine("1");
                    From.Flush();
                    
                    //user in hashtable stoppen en beginnen met luisteren naar berichten
                    Monitor.AddUser(tcpClient, User);
                }
            }
            //als de username leeg is
            else
            {
                CloseConnection();
                return;
            }

            try
            {
                //wachten op message van user
                while ((Response = Recieve.ReadLine()) != "")
                {
                    //als het niet valid is, remove gebruiker
                    if (Response == null)
                    {
                        Monitor.RemoveUser(tcpClient);
                    }
                    else
                    {
                        //verstuur bericht naar iedereen
                        Monitor.SendMessage(User, Response);
                    }
                }
            }
            catch
            {
                //als er iets fout gaat disconnect user
                Monitor.RemoveUser(tcpClient);
            }
        }
    }
}